/**
 * @file port_xf_ota_client.c
 * @author catcatBlue (catcatblue@qq.com)
 * @brief
 * @version 1.0
 * @date 2024-12-19
 *
 * Copyright (c) 2024, CorAL. All rights reserved.
 *
 */

/* ==================== [Includes] ========================================== */

#if CONFIG_PORT_OTA_ENABLE

#include "string.h"

#include "xf_fal.h"

#include "drvs.h"

#include "xf_ota_client.h"

#include "port_flash_config.h"


/* ==================== [Defines] =========================================== */

/* ==================== [Typedefs] ========================================== */

typedef struct port_xf_ota_client_ctx_s {
    uint32_t    write_len;
    uint32_t    package_len;

    uint32_t    bank_curr;
    uint32_t    bank_next;

    uint8_t     b_inited;
    // uint8_t     b_sequential_write;
} port_xf_ota_client_ctx_t;

/* ==================== [Static Prototypes] ================================= */

/* ==================== [Static Variables] ================================== */

static const char *const TAG = "port_xf_ota_client";

static const xf_fal_partition_t sc_ota_partition_table[] = {
    {OTA_BANK_A_NAME, INTERNAL_FLASH_NAME, OTA_BANK_A, OTA_BANK_B - OTA_BANK_A},
    {OTA_BANK_B_NAME, INTERNAL_FLASH_NAME, OTA_BANK_B, INTERNAL_FLASH_LEN - OTA_BANK_B},
};

static port_xf_ota_client_ctx_t s_port_xf_ota_client_ctx = {0};
static port_xf_ota_client_ctx_t *sp_port_xf_ota_client_ctx = &s_port_xf_ota_client_ctx;
#define ctx() sp_port_xf_ota_client_ctx

/* ==================== [Macros] ============================================ */

/* ==================== [Global Functions] ================================== */

xf_err_t xf_ota_init(void)
{
    xf_err_t xf_ret = XF_OK;

    if (ctx()->b_inited) {
        return XF_OK;
    }

#if (CFG_USE_LOAD)
    ctx()->bank_next = OTA_BANK_B;
#else
    uint32_t curr_code_addr = RD_32(FLASH_INFO_CODE_ADDR);
    XF_LOGD(TAG, "Curr Addr:0x%x", curr_code_addr);
    switch (curr_code_addr) {
    case OTA_BANK_A_BASE: {
        ctx()->bank_curr = OTA_BANK_A;
        ctx()->bank_next = OTA_BANK_B;
    } break;
    case OTA_BANK_B_BASE: {
        ctx()->bank_curr = OTA_BANK_B;
        ctx()->bank_next = OTA_BANK_A;
    } break;
    default: {
        XF_LOGE(TAG, "Invalid Curr Addr:0x%x", (int)curr_code_addr);
        xf_ret = XF_FAIL;
        goto l_xf_ret;
    }
    }
#endif

    xf_ret = xf_fal_register_partition_table(
                 sc_ota_partition_table,
                 ARRAY_SIZE(sc_ota_partition_table));
    if (XF_OK != xf_ret) {
        XF_LOGE(TAG, "xf_fal_register_partition_table:%s",
                xf_err_to_name(xf_ret));
        goto l_xf_ret;
    }

    xf_ret = xf_fal_check_and_update_cache();
    if (XF_OK != xf_ret) {
        XF_LOGE(TAG, "xf_fal_check_and_update_cache:%s",
                xf_err_to_name(xf_ret));
        goto l_xf_ret;
    }
    xf_fal_show_part_table();

    xf_ret = xf_fal_init();
    if ((XF_OK != xf_ret)
            && (XF_ERR_INITED != xf_ret)
       ) {
        XF_LOGE(TAG, "xf_fal_init:%s",
                xf_err_to_name(xf_ret));
        goto l_xf_ret;
    }

    ctx()->b_inited = true;

l_xf_ret:;
    return xf_ret;
}

xf_err_t xf_ota_deinit(void)
{
    xf_err_t xf_ret = XF_OK;

    if (!ctx()->b_inited) {
        return XF_OK;
    }

    xf_ret = xf_fal_unregister_partition_table(
                 sc_ota_partition_table);
    if (XF_OK != xf_ret) {
        XF_LOGE(TAG, "xf_fal_unregister_partition_table:%s",
                xf_err_to_name(xf_ret));
        goto l_xf_ret;
    }

    xf_ret = xf_fal_check_and_update_cache();
    if (XF_OK != xf_ret) {
        XF_LOGE(TAG, "xf_fal_check_and_update_cache:%s",
                xf_err_to_name(xf_ret));
        goto l_xf_ret;
    }

    ctx()->b_inited = false;

l_xf_ret:;
    return xf_ret;
}

xf_err_t xf_ota_get_running_partition(xf_ota_partition_t *p_part_hdl)
{
    xf_err_t                    xf_ret      = XF_OK;
    const xf_fal_partition_t   *p_part;

    XF_CHECK(!ctx()->b_inited, XF_ERR_UNINIT,
             TAG, "b_inited:%s", xf_err_to_name(XF_ERR_UNINIT));
    XF_CHECK(NULL == p_part_hdl, XF_ERR_INVALID_ARG,
             TAG, "p_part_hdl:%s", xf_err_to_name(XF_ERR_INVALID_ARG));

    switch (ctx()->bank_curr) {
    case OTA_BANK_A: {
        p_part = xf_fal_partition_find(OTA_BANK_A_NAME);
        p_part_hdl->partition_id = XF_OTA_PARTITION_ID_OTA_0;
    } break;
    case OTA_BANK_B: {
        p_part = xf_fal_partition_find(OTA_BANK_B_NAME);
        p_part_hdl->partition_id = XF_OTA_PARTITION_ID_OTA_1;
    } break;
    default: {
        XF_LOGE(TAG, "Invalid bank_curr:0x%x", (int)ctx()->bank_curr);
        xf_ret = XF_FAIL;
        goto l_xf_ret;
    }
    }

    if (NULL == p_part) {
        XF_LOGE(TAG, "xf_fal_partition_find:%s",
                xf_err_to_name(XF_ERR_INVALID_ARG));
        xf_ret = XF_FAIL;
        goto l_xf_ret;
    }

    p_part_hdl->platform_data   = (void *)p_part;
    p_part_hdl->is_xf_fal_part  = true;

l_xf_ret:;
    return xf_ret;
}

xf_err_t xf_ota_get_next_update_partition(
    xf_ota_partition_t *p_start_part_hdl, xf_ota_partition_t *p_next_part_hdl)
{
    xf_err_t                    xf_ret      = XF_OK;
    const xf_fal_partition_t   *p_part;

    UNUSED(p_start_part_hdl);
    XF_CHECK(!ctx()->b_inited, XF_ERR_UNINIT,
             TAG, "b_inited:%s", xf_err_to_name(XF_ERR_UNINIT));
    XF_CHECK(NULL == p_next_part_hdl, XF_ERR_INVALID_ARG,
             TAG, "p_part_hdl:%s", xf_err_to_name(XF_ERR_INVALID_ARG));

    switch (ctx()->bank_next) {
    case OTA_BANK_A: {
        p_part = xf_fal_partition_find(OTA_BANK_A_NAME);
        p_next_part_hdl->partition_id = XF_OTA_PARTITION_ID_OTA_0;
    } break;
    case OTA_BANK_B: {
        p_part = xf_fal_partition_find(OTA_BANK_B_NAME);
        p_next_part_hdl->partition_id = XF_OTA_PARTITION_ID_OTA_1;
    } break;
    default: {
        XF_LOGE(TAG, "Invalid bank_next:0x%x", (int)ctx()->bank_next);
        xf_ret = XF_FAIL;
        goto l_xf_ret;
    }
    }

    if (NULL == p_part) {
        XF_LOGE(TAG, "xf_fal_partition_find:%s",
                xf_err_to_name(XF_ERR_INVALID_ARG));
        xf_ret = XF_FAIL;
        goto l_xf_ret;
    }

    p_next_part_hdl->platform_data   = (void *)p_part;
    p_next_part_hdl->is_xf_fal_part  = true;

l_xf_ret:;
    return xf_ret;
}

xf_err_t xf_ota_get_partition_info(
    const xf_ota_partition_t *p_part_hdl, xf_ota_partition_info_t *p_info)
{
    xf_err_t                    xf_ret      = XF_OK;
    const xf_fal_partition_t   *p_part;

    XF_CHECK(NULL == p_part_hdl, XF_ERR_INVALID_ARG,
             TAG, "p_part_hdl:%s", xf_err_to_name(XF_ERR_INVALID_ARG));
    XF_CHECK(NULL == p_part_hdl->platform_data, XF_ERR_INVALID_ARG,
             TAG, "platform_data:%s", xf_err_to_name(XF_ERR_INVALID_ARG));
    XF_CHECK(NULL == p_info, XF_ERR_INVALID_ARG,
             TAG, "p_info:%s", xf_err_to_name(XF_ERR_INVALID_ARG));

    p_part = (const xf_fal_partition_t *)p_part_hdl->platform_data;

    p_info->unit_size   = 4;
    p_info->size        = p_part->len;

    return xf_ret;
}

size_t xf_ota_get_platform_app_desc_size(void)
{
    return 0;
}

xf_err_t xf_ota_get_platform_app_desc_block(
    xf_ota_partition_t *p_part_hdl, void *p_desc_out, size_t buff_size)
{
    return XF_ERR_NOT_SUPPORTED;
}

size_t xf_ota_get_platform_app_digest_size(void)
{
    return 0;
}

xf_err_t xf_ota_get_platform_app_digest_block(
    xf_ota_partition_t *p_part_hdl, void *p_digest_out, size_t buff_size)
{
    return XF_ERR_NOT_SUPPORTED;
}

// xf_err_t xf_ota_get_xf_app_desc(
//     xf_ota_partition_t *p_part_hdl, xf_app_desc_t *p_desc_out);

xf_err_t xf_ota_start(
    xf_ota_partition_t *p_part_hdl,
    uint32_t package_len, bool sequential_write, xf_ota_t *p_hdl)
{
    xf_err_t                    xf_ret      = XF_OK;
    const xf_fal_partition_t   *p_part;

    XF_CHECK(NULL == p_part_hdl, XF_ERR_INVALID_ARG,
             TAG, "p_part_hdl:%s", xf_err_to_name(XF_ERR_INVALID_ARG));
    XF_CHECK(NULL == p_part_hdl->platform_data, XF_ERR_INVALID_ARG,
             TAG, "platform_data:%s", xf_err_to_name(XF_ERR_INVALID_ARG));
    XF_CHECK(NULL == p_hdl, XF_ERR_INVALID_ARG,
             TAG, "p_hdl:%s", xf_err_to_name(XF_ERR_INVALID_ARG));

    p_part = (const xf_fal_partition_t *)p_part_hdl->platform_data;
    /* 不支持 package_len == XF_OTA_PACKAGE_LEN_UNKNOWN */
    if (package_len > p_part->len) {
        XF_LOGE(TAG, "Invalid package_len:%d", (int)package_len);
        xf_ret = XF_FAIL;
        goto l_xf_ret;
    }
    ctx()->package_len = package_len;
    UNUSED(sequential_write);
    p_hdl->platform_data = (void *)p_part;
    ctx()->write_len = 0;

l_xf_ret:;
    return xf_ret;
}

xf_err_t xf_ota_abort(xf_ota_t *p_hdl)
{
    XF_CHECK(NULL == p_hdl, XF_ERR_INVALID_ARG,
             TAG, "p_hdl:%s", xf_err_to_name(XF_ERR_INVALID_ARG));

    /* TODO */

    return XF_OK;
}

xf_err_t xf_ota_end(xf_ota_t *p_hdl)
{
    XF_CHECK(NULL == p_hdl, XF_ERR_INVALID_ARG,
             TAG, "p_hdl:%s", xf_err_to_name(XF_ERR_INVALID_ARG));

    /* TODO 目前没有需要校验的 */

    return XF_OK;
}

xf_err_t xf_ota_upgrade(xf_ota_partition_t *p_part_hdl, bool reboot)
{
    UNUSED(p_part_hdl);
    XF_CHECK(NULL == p_part_hdl, XF_ERR_INVALID_ARG,
             TAG, "p_part_hdl:%s", xf_err_to_name(XF_ERR_INVALID_ARG));
    
    /* TODO  256 字节，可能需要改用堆空间 */
    uint32_t ota_wr_data[FLASH_PAGE_SIZE_WLEN];

#if (CFG_USE_LOAD)
    /* TODO CFG_USE_LOAD */
    memset((uint8_t *)ota_wr_data, 0xFF, sizeof(ota_wr_data));
    ota_wr_data[0] = 0x55AA5AA5;
    ota_wr_data[1] = ctx()->package_len;
    ota_wr_data[2] = OTA_BANK_B_BASE;

    flash_page_erase(OTA_BANK_B - INTERNAL_FLASH_PAGE_SIZE);
    flash_write(OTA_BANK_B - INTERNAL_FLASH_PAGE_SIZE, ota_wr_data, FLASH_PAGE_SIZE_WLEN);
#else
    flash_wr_protect(0x00);

    flash_read(FLASH_INFO_PAGE, ota_wr_data, FLASH_PAGE_SIZE_WLEN);
    /* ((uint32_t *)LoadInfo)[1]: CodeLength    烧录代码长度 */
    /* ((uint32_t *)LoadInfo)[2]: CodeAddress   代码烧录在Flash的绝对地址 */
    ota_wr_data[1] = ctx()->package_len;
    ota_wr_data[2] = ctx()->bank_next | FLASH_BASE;

    flash_page_erase(FLASH_INFO_PAGE);
    flash_write(FLASH_INFO_PAGE, ota_wr_data, FLASH_PAGE_SIZE_WLEN);

    flash_wr_protect(0x64);
#endif

    if (reboot) {
        NVIC_SystemReset();
    }

    return XF_OK;
}

xf_err_t xf_ota_write(xf_ota_t *p_hdl, const void *src, size_t size)
{
    xf_err_t                    xf_ret      = XF_OK;
    const xf_fal_partition_t   *p_part;
    size_t                      erase_size;

    XF_CHECK(NULL == p_hdl, XF_ERR_INVALID_ARG,
             TAG, "p_hdl:%s", xf_err_to_name(XF_ERR_INVALID_ARG));
    XF_CHECK(NULL == p_hdl->platform_data, XF_ERR_INVALID_ARG,
             TAG, "platform_data:%s", xf_err_to_name(XF_ERR_INVALID_ARG));
    XF_CHECK(NULL == src, XF_ERR_INVALID_ARG,
             TAG, "src:%s", xf_err_to_name(XF_ERR_INVALID_ARG));
    XF_CHECK(0 == size, XF_ERR_INVALID_ARG,
             TAG, "size:%s", xf_err_to_name(XF_ERR_INVALID_ARG));

    p_part = (const xf_fal_partition_t *)p_hdl->platform_data;

    erase_size = ALIGN_UP(size, INTERNAL_FLASH_PAGE_SIZE);
    if (erase_size != size) {
        XF_LOGD(TAG, "erase_size:%d, size:%d", (int)erase_size, (int)size);
    }

    xf_ret = xf_fal_partition_erase(
                 p_part, ctx()->write_len, erase_size);
    if (xf_ret != XF_OK) {
        XF_LOGE(TAG, "partition_write:%s", xf_err_to_name(xf_ret));
        goto l_xf_ret;
    }
    xf_ret = xf_fal_partition_write(
                 p_part, ctx()->write_len, src, size);
    if (xf_ret != XF_OK) {
        XF_LOGE(TAG, "partition_write:%s", xf_err_to_name(xf_ret));
        goto l_xf_ret;
    }
    ctx()->write_len += size;

l_xf_ret:;
    return xf_ret;
}

xf_err_t xf_ota_write_to(
    xf_ota_t *p_hdl, size_t dst_offset, const void *src, size_t size)
{
    xf_err_t                    xf_ret      = XF_OK;
    const xf_fal_partition_t   *p_part;
    size_t                      erase_size;

    XF_CHECK(NULL == p_hdl, XF_ERR_INVALID_ARG,
             TAG, "p_hdl:%s", xf_err_to_name(XF_ERR_INVALID_ARG));
    XF_CHECK(NULL == p_hdl->platform_data, XF_ERR_INVALID_ARG,
             TAG, "platform_data:%s", xf_err_to_name(XF_ERR_INVALID_ARG));
    XF_CHECK(NULL == src, XF_ERR_INVALID_ARG,
             TAG, "src:%s", xf_err_to_name(XF_ERR_INVALID_ARG));
    XF_CHECK(0 == size, XF_ERR_INVALID_ARG,
             TAG, "size:%s", xf_err_to_name(XF_ERR_INVALID_ARG));

    p_part = (const xf_fal_partition_t *)p_hdl->platform_data;

    erase_size = ALIGN_UP(size, INTERNAL_FLASH_PAGE_SIZE);
    if (erase_size != size) {
        XF_LOGD(TAG, "erase_size:%d, size:%d", (int)erase_size, (int)size);
    }

    xf_ret = xf_fal_partition_erase(
                 p_part, dst_offset, erase_size);
    if (xf_ret != XF_OK) {
        XF_LOGE(TAG, "partition_write:%s", xf_err_to_name(xf_ret));
        goto l_xf_ret;
    }

    xf_ret = xf_fal_partition_write(
                 p_part, dst_offset, src, size);
    if (xf_ret != XF_OK) {
        XF_LOGE(TAG, "partition_write:%s", xf_err_to_name(xf_ret));
        goto l_xf_ret;
    }

l_xf_ret:;
    return xf_ret;
}

/* ==================== [Static Functions] ================================== */

#endif /* CONFIG_PORT_OTA_ENABLE */
